from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, List, Optional

# Assignment abstract base class
class Assignable(ABC):


    def __init__(self,assignment_id: str,title: str, description: str,course_id: str,max_score: float,due_date: datetime,created_by: Optional[str] = None):
        self.assignment_id = assignment_id
        self.title = title
        self.description = description
        self.course_id = course_id
        self.max_score = float(max_score)
        self.due_date = due_date
        self.created_at = datetime.now()
        self.created_by = created_by
        self.status = "active"

    # Compatible with old interfaces (CLI and other modules expect these accessors)
    def get_id(self) -> str:
        return self.assignment_id

    def get_title(self) -> str:
        return self.title

    def get_description(self) -> str:
        return self.description

    def get_course_id(self) -> str:
        return self.course_id

    def get_max_score(self) -> float:
        return self.max_score

    def is_overdue(self) -> bool:
        return datetime.now() > self.due_date

    def update_info(self, info: Dict[str, Any]) -> bool:
        for k, v in info.items():
            if hasattr(self, k):
                setattr(self, k, v)
        return True

    def close(self) -> None:
        self.status = "closed"

    def get_basic_info(self) -> Dict[str, Any]:
        return {
            "assignment_id": self.assignment_id,
            "title": self.title,
            "course_id": self.course_id,
            "max_score": self.max_score,
            "due_date": self.due_date if isinstance(self.due_date, str) else self.due_date.isoformat(),
            "status": self.status,
        }
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary format - compatible with serialization"""
        return self.get_basic_info()

    @abstractmethod
    def get_details(self) -> Dict[str, Any]:
        pass

    @abstractmethod
    def validate_submission(self, content: Any) -> bool:
        pass

    @abstractmethod
    def get_type(self) -> str:
        pass


class Assignment(Assignable):
    def __init__(self,
                 assignment_id: str,
                 title: str,
                 description: str,
                 course_id: str,
                 max_score: float,
                 due_date: datetime,
                 file_required: bool = False,
                 allowed_formats: Optional[List[str]] = None,
                 max_file_size: Optional[int] = None,
                 instructions: Optional[str] = None,
                 created_by: Optional[str] = None):
        super().__init__(assignment_id, title, description, course_id, max_score, due_date, created_by)
        self.file_required = file_required
        self.allowed_formats = allowed_formats or []
        self.max_file_size = max_file_size
        self.instructions = instructions or ""

    def get_details(self) -> Dict[str, Any]:
        d = self.get_basic_info()
        d.update({
            "type": self.get_type(),
            "file_required": self.file_required,
            "allowed_formats": self.allowed_formats,
            "instructions": self.instructions,
        })
        return d

    def validate_submission(self, content: Any) -> bool:
        # content expected to be dict, if file_required then must include 'file'
        if not isinstance(content, dict):
            return False
        if self.file_required and "file" not in content:
            return False
        # If no file required, accept text content
        if not self.file_required and ("text" in content or "content" in content):
            return True
        # optional format check
        if "file" in content and self.allowed_formats:
            filename = content.get("file", "")
            if not any(filename.endswith(f) for f in self.allowed_formats):
                return False
        return True

    def get_type(self) -> str:
        return "assignment"
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'Assignment':
        """Create Assignment object from dictionary"""
        # Handle due_date field
        due_date = data.get('due_date')
        if isinstance(due_date, str):
            due_date = datetime.fromisoformat(due_date.replace('Z', '+00:00'))
        
        return cls(
            assignment_id=data['assignment_id'],
            title=data['title'],
            description=data.get('description', ''),
            course_id=data['course_id'],
            max_score=float(data['max_score']),
            due_date=due_date,
            file_required=data.get('file_required', False),
            allowed_formats=data.get('allowed_formats'),
            max_file_size=data.get('max_file_size'),
            instructions=data.get('instructions', ''),
            created_by=data.get('created_by')
        )


class Quiz(Assignable):
    def __init__(self,
                 assignment_id: str,
                 title: str,
                 description: str,
                 course_id: str,
                 max_score: float,
                 due_date: datetime,
                 questions: Optional[List[Dict[str, Any]]] = None,
                 time_limit: Optional[int] = None,
                 attempts_allowed: int = 1,
                 shuffle_questions: bool = False,
                 created_by: Optional[str] = None):
        super().__init__(assignment_id, title, description, course_id, max_score, due_date, created_by)
        self.questions = questions or []
        self.time_limit = time_limit
        self.attempts_allowed = attempts_allowed
        self.shuffle_questions = shuffle_questions

    def get_details(self) -> Dict[str, Any]:
        d = self.get_basic_info()
        d.update({
            "type": self.get_type(),
            "questions_count": len(self.questions),
        })
        return d

    def validate_submission(self, content: Any) -> bool:
        # expect dict mapping question_id -> answer
        if not isinstance(content, dict):
            return False
        # check at least has answers for questions
        qids = {q.get("id") for q in self.questions}
        return qids.issubset(set(content.keys())) or len(content) >= len(self.questions)

    def get_type(self) -> str:
        return "quiz"

    def add_question(self, question: Dict[str, Any]) -> bool:
        if not isinstance(question, dict) or "id" not in question:
            return False
        self.questions.append(question)
        return True

    def remove_question(self, question_id: str) -> bool:
        before = len(self.questions)
        self.questions = [q for q in self.questions if q.get("id") != question_id]
        return len(self.questions) < before

    def auto_grade(self, answers: Dict[str, Any]) -> float:
        # simple auto grading: sum points of correct answers
        if not isinstance(answers, dict):
            return 0.0
        total = 0.0
        max_points = 0.0
        for q in self.questions:
            pts = float(q.get("points", 1))
            max_points += pts
            qid = q.get("id")
            if qid in answers and answers[qid] == q.get("correct_answer"):
                total += pts
        if max_points == 0:
            return 0.0
        # scale to assignment max_score
        return (total / max_points) * self.max_score


class Project(Assignable):
    def __init__(self,
                 assignment_id: str,
                 title: str,
                 description: str,
                 course_id: str,
                 max_score: float,
                 due_date: datetime,
                 milestones: Optional[List[Dict[str, Any]]] = None,
                 group_size: int = 1,
                 resources: Optional[List[str]] = None,
                 rubric: Optional[Dict[str, Any]] = None,
                 created_by: Optional[str] = None):
        super().__init__(assignment_id, title, description, course_id, max_score, due_date, created_by)
        self.milestones = milestones or []
        self.group_size = group_size
        self.resources = resources or []
        self.rubric = rubric or {}

    def get_details(self) -> Dict[str, Any]:
        d = self.get_basic_info()
        d.update({
            "type": self.get_type(),
            "milestones_count": len(self.milestones),
        })
        return d

    def validate_submission(self, content: Any) -> bool:
        # expect dict with 'report' or 'files'
        if not isinstance(content, dict):
            return False
        return "report" in content or "files" in content

    def get_type(self) -> str:
        return "project"